package com.newlink.building.notifications

import android.app.Notification
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.net.Uri
import android.os.Build
import android.webkit.MimeTypeMap
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.app.Person
import androidx.core.app.RemoteInput
import androidx.core.content.ContextCompat
import androidx.core.content.LocusIdCompat
import androidx.core.graphics.drawable.IconCompat
import com.alibaba.android.arouter.core.LogisticsCenter
import com.alibaba.android.arouter.launcher.ARouter
import com.newlink.building.common_base.constant.NL_Key
import com.newlink.building.common_base.constant.Base_RouterPath
import com.newlink.building.common_base.utils.NLog
import com.newlink.building.NL_App.Companion.coreContext
import com.newlink.building.NL_App.Companion.corePreferences
import com.newlink.building.R
import com.newlink.building.activities.Module_Phone_MonitorActivityNL
import com.newlink.building.activities.call.Module_Phone_CallActivity
import com.newlink.building.activities.call.Module_Phone_IncomingCallActivity
import com.newlink.building.activities.call.Module_Phone_OutgoingCallActivity
import com.newlink.building.compatibility.Module_Phone_Compatibility
import com.newlink.building.compatibility.getPerson
import com.newlink.building.compatibility.getThumbnailUri
import com.newlink.building.core.Module_Phone_CoreService
import com.newlink.building.utils.Module_Phone_AppUtils
import com.newlink.building.utils.Module_Phone_FileUtils
import com.newlink.building.utils.Module_Phone_ImageUtils
import com.newlink.building.utils.Module_Phone_LinphoneUtils
import org.linphone.core.Address
import org.linphone.core.Call
import org.linphone.core.ChatMessage
import org.linphone.core.ChatMessageListener
import org.linphone.core.ChatMessageListenerStub
import org.linphone.core.ChatRoom
import org.linphone.core.ChatRoomCapabilities
import org.linphone.core.Core
import org.linphone.core.CoreListenerStub
import org.linphone.core.Friend
import org.linphone.core.Reason
import org.linphone.core.tools.Log
import java.util.Locale


/**
 * 通知相关
 * Created by xiaodong on 2022/9/16.
 */
class Notifiable(val notificationId: Int) {
    val messages: ArrayList<NotifiableMessage> = arrayListOf()

    var isGroup: Boolean = false
    var groupTitle: String? = null
    var localIdentity: String? = null
    var myself: String? = null
    var remoteAddress: String? = null
}

class NotifiableMessage(
    var message: String,
    val friend: Friend?,
    val sender: String,
    val time: Long,
    val senderAvatar: Bitmap? = null,
    var filePath: Uri? = null,
    var fileMime: String? = null,
    val isOutgoing: Boolean = false
)

class NotificationsManager(private val context: Context) {
    companion object {
        const val CHAT_NOTIFICATIONS_GROUP = "CHAT_NOTIF_GROUP"
        const val KEY_TEXT_REPLY = "key_text_reply"
        const val INTENT_NOTIF_ID = "NOTIFICATION_ID"
        const val INTENT_REPLY_NOTIF_ACTION = "com.vdroid.dooraccess.module_linphone.REPLY_ACTION"
        const val INTENT_HANGUP_CALL_NOTIF_ACTION = "org.linphone.HANGUP_CALL_ACTION"
        const val INTENT_ANSWER_CALL_NOTIF_ACTION = "org.linphone.ANSWER_CALL_ACTION"
        const val INTENT_MARK_AS_READ_ACTION = "org.linphone.MARK_AS_READ_ACTION"
        const val INTENT_LOCAL_IDENTITY = "LOCAL_IDENTITY"
        const val INTENT_REMOTE_ADDRESS = "REMOTE_ADDRESS"

        private const val SERVICE_NOTIF_ID = 1
        private const val MISSED_CALLS_NOTIF_ID = 2

        const val CHAT_TAG = "Chat"
        private const val MISSED_CALL_TAG = "Missed call"
    }

    private val notificationManager: NotificationManagerCompat by lazy {
        NotificationManagerCompat.from(context)
    }
    private val chatNotificationsMap: HashMap<String, Notifiable> = HashMap()
    private val callNotificationsMap: HashMap<String, Notifiable> = HashMap()
    private val previousChatNotifications: ArrayList<Int> = arrayListOf()
    private val chatBubbleNotifications: ArrayList<Int> = arrayListOf()

    private var currentForegroundServiceNotificationId: Int = 0
    private var serviceNotification: Notification? = null

    var service: Module_Phone_CoreService? = null

    var currentlyDisplayedChatRoomAddress: String? = null

    private val listener: CoreListenerStub = object : CoreListenerStub() {
        override fun onCallStateChanged(
            core: Core,
            call: Call,
            state: Call.State,
            message: String
        ) {

            if (corePreferences.preventInterfaceFromShowingUp) {
                Log.w("[Context] We were asked to not show the call notifications")
                return
            }

            when (call.state) {
                Call.State.IncomingEarlyMedia, Call.State.IncomingReceived -> {
//                    val callNb = coreContext.core.callsNb
//                    if (callNb > 0) {
//                        Log.e("<Jake> 当前通话数量 : $callNb")
//                    } else {
                    NLog.e("  show incoming notification")
                    displayIncomingCallNotification(call)
//                    }
                }

                Call.State.End, Call.State.Error -> dismissCallNotification(call)
                Call.State.Released -> {
                    if (Module_Phone_LinphoneUtils.isCallLogMissed(call.callLog)) {
                        displayMissedCallNotification(call.remoteAddress)
                    }
                }

                else -> displayCallNotification(call)
            }
        }

        override fun onMessageReceived(core: Core, room: ChatRoom, message: ChatMessage) {
            if (message.isOutgoing || corePreferences.disableChat) return

            if (corePreferences.preventInterfaceFromShowingUp) {
                Log.w("[Context] We were asked to not show the chat notifications")
                return
            }

            if (currentlyDisplayedChatRoomAddress == room.peerAddress.asStringUriOnly()) {
                Log.i("[Notifications Manager] Chat room is currently displayed, do not notify received message")
                // Mark as read is now done in the DetailChatRoomFragment
                return
            }

            if (message.errorInfo.reason == Reason.UnsupportedContent) {
                Log.w("[Notifications Manager] Received message with unsupported content, do not notify")
                return
            }

            if (message.contents.find { content ->
                    content.isFile or content.isFileTransfer or content.isText
                } == null
            ) {
                Log.w("[Notifications Manager] Received message with neither text or attachment, do not notify")
                return
            }

            if (corePreferences.chatRoomShortcuts) {
                Log.i("[Notifications Manager] Ensure chat room shortcut exists for bubble notification")
                Module_Phone_Compatibility.createShortcutsToChatRooms(context)
            }

            displayIncomingChatNotification(room, message)
        }

        override fun onChatRoomRead(core: Core, chatRoom: ChatRoom) {
            val address = chatRoom.peerAddress.asStringUriOnly()
            val notifiable = chatNotificationsMap[address]
            if (notifiable != null) {
                if (chatBubbleNotifications.contains(notifiable.notificationId)) {
                    Log.i("[Notifications Manager] Chat room [$chatRoom] has been marked as read, not removing notification because of a chat bubble")
                } else {
                    Log.i("[Notifications Manager] Chat room [$chatRoom] has been marked as read, removing notification if any")
                    dismissChatNotification(chatRoom)
                }
            } else {
                val notificationId = chatRoom.creationTime.toInt()
                if (chatBubbleNotifications.contains(notificationId)) {
                    Log.i("[Notifications Manager] Chat room [$chatRoom] has been marked as read but no notifiable found, not removing notification because of a chat bubble")
                } else {
                    Log.i("[Notifications Manager] Chat room [$chatRoom] has been marked as read but no notifiable found, removing notification if any")
                    dismissChatNotification(chatRoom)
                }
            }
        }

        override fun onLastCallEnded(core: Core) {
            Log.i("[Notifications Manager] Last call ended, make sure foreground service is stopped and notification removed")
            stopCallForeground()
        }
    }

    val chatListener: ChatMessageListener = object : ChatMessageListenerStub() {
        override fun onMsgStateChanged(message: ChatMessage, state: ChatMessage.State) {
            message.userData ?: return
            val id = message.userData as Int
            Log.i("[Notifications Manager] Reply message state changed [$state] for id $id")

            if (state != ChatMessage.State.InProgress) {
                // No need to be called here twice
                message.removeListener(this)
            }

            if (state == ChatMessage.State.Delivered || state == ChatMessage.State.Displayed) {
                val address = message.chatRoom.peerAddress.asStringUriOnly()
                val notifiable = chatNotificationsMap[address]
                if (notifiable != null) {
                    if (notifiable.notificationId != id) {
                        Log.w("[Notifications Manager] ID doesn't match: ${notifiable.notificationId} != $id")
                    }
                    displayReplyMessageNotification(message, notifiable)
                } else {
                    Log.e("[Notifications Manager] Couldn't find notification for chat room $address")
                    cancel(id, CHAT_TAG)
                }
            } else if (state == ChatMessage.State.NotDelivered) {
                Log.e("[Notifications Manager] Reply wasn't delivered")
                cancel(id, CHAT_TAG)
            }
        }
    }

    init {
        Module_Phone_Compatibility.createNotificationChannels(context, notificationManager)

        val manager =
            context.getSystemService(NotificationManager::class.java) as NotificationManager
        for (notification in manager.activeNotifications) {
            if (notification.tag.isNullOrEmpty()) { // We use null tag for call notifications otherwise it will create duplicates when used with Service.startForeground()...
                Log.w("[Notifications Manager] Found existing call? notification [${notification.id}], cancelling it")
                manager.cancel(notification.tag, notification.id)
            } else if (notification.tag == CHAT_TAG) {
                Log.i("[Notifications Manager] Found existing chat notification [${notification.id}]")
                previousChatNotifications.add(notification.id)
            }
        }
    }

    fun onCoreReady() {
        coreContext.core.addListener(listener)
    }

    fun destroy() {
        // Don't use cancelAll to keep message notifications !
        // When a message is received by a push, it will create a CoreService
        // but it might be getting killed quite quickly after that
        // causing the notification to be missed by the user...
        Log.i("[Notifications Manager] Getting destroyed, clearing foreground Service & call notifications")

        if (currentForegroundServiceNotificationId > 0 && !corePreferences.keepServiceAlive) {
            Log.i("[Notifications Manager] Clearing foreground Service")
            stopForegroundNotification()
        }

        if (callNotificationsMap.size > 0) {
            Log.i("[Notifications Manager] Clearing call notifications")
            for (notifiable in callNotificationsMap.values) {
                notificationManager.cancel(notifiable.notificationId)
            }
        }

        coreContext.core.removeListener(listener)
    }

    private fun notify(id: Int, notification: Notification, tag: String? = null) {
        Log.i("[Notifications Manager] Notifying [$id] with tag [$tag]")
        // todo Android 12 问题先隐藏 FLAG_IMMUTABLE
        notificationManager.notify(tag, id, notification)
    }

    fun cancel(id: Int, tag: String? = null) {
        Log.i("[Notifications Manager] Canceling [$id] with tag [$tag]")
        notificationManager.cancel(tag, id)
    }

    fun resetChatNotificationCounterForSipUri(sipUri: String) {
        val notifiable: Notifiable? = chatNotificationsMap[sipUri]
        notifiable?.messages?.clear()
    }

    /* Service related */

    fun startForeground() {
        NLog.e("[jake] >>>  startForeground <<<< ************")
        val serviceChannel = context.getString(R.string.notification_channel_service_id)
        if (Module_Phone_Compatibility.getChannelImportance(
                notificationManager,
                serviceChannel
            ) == NotificationManagerCompat.IMPORTANCE_NONE
        ) {
            Log.w("[Notifications Manager] Service channel is disabled!")
            return
        }

        val coreService = service
        if (coreService != null) {
            startForeground(coreService, useAutoStartDescription = false)
        } else {
            Log.w("[Notifications Manager] Can't start service as foreground without a service, starting it now")
            val intent = Intent()
            intent.setClass(coreContext.context, Module_Phone_CoreService::class.java)
            try {
                Module_Phone_Compatibility.startForegroundService(coreContext.context, intent)
            } catch (ise: IllegalStateException) {
                Log.e("[Notifications Manager] Failed to start Service: $ise")
            } catch (se: SecurityException) {
                Log.e("[Notifications Manager] Failed to start Service: $se")
            }
        }
    }

    fun startCallForeground(coreService: Module_Phone_CoreService) {
        service = coreService
        when {
            currentForegroundServiceNotificationId != 0 -> {
                if (currentForegroundServiceNotificationId != SERVICE_NOTIF_ID) {
                    Log.e("[Notifications Manager] There is already a foreground service notification [$currentForegroundServiceNotificationId]")
                } else {
                    Log.i("[Notifications Manager] There is already a foreground service notification, no need to use the call notification to keep Service alive")
                }
            }

            coreContext.core.callsNb > 0 -> {
                // When this method will be called, we won't have any notification yet
                val call = coreContext.core.currentCall ?: coreContext.core.calls[0]
                when (call.state) {
                    Call.State.IncomingReceived, Call.State.IncomingEarlyMedia -> {
                        Log.i("[Notifications Manager] Waiting for call to be in state Connected before creating service notification")
                    }

                    else -> {
                        Log.i("[Notifications Manager] Creating call notification to be used as foreground service")
                        displayCallNotification(call, true)
                    }
                }
            }
        }
    }

    fun startForeground(coreService: Module_Phone_CoreService, useAutoStartDescription: Boolean = true) {
        if (serviceNotification == null) {
            createServiceNotification(useAutoStartDescription)
            if (serviceNotification == null) {
                Log.e("[Notifications Manager] Failed to create service notification, aborting foreground service!")
                return
            }
        }
        currentForegroundServiceNotificationId = SERVICE_NOTIF_ID
        Log.i("[Notifications Manager] Starting service as foreground [$currentForegroundServiceNotificationId]")

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
            val mask = Module_Phone_Compatibility.FOREGROUND_SERVICE_TYPE_PHONE_CALL
            Module_Phone_Compatibility.startForegroundService(
                coreService,
                currentForegroundServiceNotificationId,
                serviceNotification,
                mask
            )
        } else {
            Module_Phone_Compatibility.startForegroundService(
                coreService,
                currentForegroundServiceNotificationId,
                serviceNotification
            )
        }
        service = coreService
    }

    private fun startForeground(notificationId: Int, callNotification: Notification) {
        if (currentForegroundServiceNotificationId == 0 && service != null) {
            Log.i("[Notifications Manager] Starting service as foreground using call notification [$notificationId]")
            currentForegroundServiceNotificationId = notificationId
            service?.startForeground(currentForegroundServiceNotificationId, callNotification)
        }
    }

    fun stopForegroundNotification() {
        if (service != null) {
            Log.i("[Notifications Manager] Stopping service as foreground [$currentForegroundServiceNotificationId]")
            service?.stopForeground(true)
            currentForegroundServiceNotificationId = 0
        }
    }

    fun resendForegroundNotification() {
        stopForegroundNotification()
        serviceNotification = null
        startForeground()
    }

    fun stopForegroundNotificationIfPossible() {
        if (service != null && currentForegroundServiceNotificationId == SERVICE_NOTIF_ID && !corePreferences.keepServiceAlive) {
            Log.i("[Notifications Manager] Stopping auto-started service notification [$currentForegroundServiceNotificationId]")
            stopForegroundNotification()
        }
    }

    fun stopCallForeground() {
        if (service != null && currentForegroundServiceNotificationId != SERVICE_NOTIF_ID) {
            Log.i("[Notifications Manager] Stopping call notification [$currentForegroundServiceNotificationId] used as foreground service")
            stopForegroundNotification()
        }
    }

    private fun createServiceNotification(useAutoStartDescription: Boolean = false) {
        val serviceChannel = context.getString(R.string.notification_channel_service_id)
        if (Module_Phone_Compatibility.getChannelImportance(
                notificationManager,
                serviceChannel
            ) == NotificationManagerCompat.IMPORTANCE_NONE
        ) {
            Log.w("[Notifications Manager] Service channel is disabled!")
            return
        }

//        val pendingIntent = NavDeepLinkBuilder(context)
//            .setComponentName(MainActivity::class.java)
//            .setGraph(org.linphone.core.R.navigation.main_nav_graph)
//            .setDestination(org.linphone.core.R.id.dialerFragment)
//            .createPendingIntent()
//        val postcard = ARouter.getInstance().build(RouterPath.PATH_HOME)
        val postcard = ARouter.getInstance().build(Base_RouterPath.PATH_NOTIFY)
        LogisticsCenter.completion(postcard)
        val intent = Intent(context, postcard.destination)
        NLog.e("notify => ${postcard.destination.simpleName}")
        val pendingIntent: PendingIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            PendingIntent.getActivity(context, 100, intent, PendingIntent.FLAG_IMMUTABLE)
        } else {
            PendingIntent.getActivity(
                context, 101, intent,
                PendingIntent.FLAG_IMMUTABLE
            )
        }


//        var pendingIntent: PendingIntent
//        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
//            pendingIntent = PendingIntent.getActivity(context, 100, intent, PendingIntent.FLAG_IMMUTABLE);
//        } else {
//            pendingIntent = PendingIntent.getActivity(context, 101, intent,
//                PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE);
//        }
        val builder = NotificationCompat.Builder(context, serviceChannel)
            .setContentTitle(context.getString(R.string.app_name_smart))
            .setContentText(
                if (useAutoStartDescription) context.getString(R.string.service_auto_start_description) else context.getString(
                    R.string.service_description
                )
            )
            .setSmallIcon(R.mipmap.logo)
            .setCategory(NotificationCompat.CATEGORY_SERVICE)
            .setVisibility(NotificationCompat.VISIBILITY_SECRET)
            .setWhen(System.currentTimeMillis())
            .setShowWhen(true)
            .setOngoing(true)
            .setColor(ContextCompat.getColor(context, R.color.colorPrimary))

        if (!corePreferences.preventInterfaceFromShowingUp) {
            builder.setContentIntent(pendingIntent)
        }

        serviceNotification = builder.build()
    }

    /* Call related */

    private fun getNotificationIdForCall(call: Call): Int {
        return call.callLog.startDate.toInt()
    }

    private fun getNotifiableForCall(call: Call): Notifiable {
        val address = call.remoteAddress.asStringUriOnly().split("?")[0]
        var notifiable: Notifiable? = callNotificationsMap[address]
        if (notifiable == null) {
            notifiable = Notifiable(getNotificationIdForCall(call))
            notifiable.remoteAddress = call.remoteAddress.asStringUriOnly().split("?")[0]

            callNotificationsMap[address] = notifiable
        }
        return notifiable
    }

    fun getPerson(friend: Friend?, displayName: String, picture: Bitmap?): Person {
        return if (friend != null) {
            friend.getPerson()
        } else {
            val builder = Person.Builder().setName(displayName)
            val userIcon =
                if (picture != null) {
                    IconCompat.createWithAdaptiveBitmap(picture)
                } else {
                    IconCompat.createWithResource(context, R.drawable.head_in_call)
                }
            if (userIcon != null) builder.setIcon(userIcon)
            builder.build()
        }
    }

    private fun displayIncomingCallNotification(call: Call, useAsForeground: Boolean = false) {
        if (isCurrentCallInMonitoring(call)) return
        if (coreContext.declineCallDueToGsmActiveCall()) {
            Log.w("[Notifications Manager] Call will be declined, do not show incoming call notification")
            return
        }

        try {
            val showLockScreenNotification = android.provider.Settings.Secure.getInt(
                context.contentResolver,
                "lock_screen_show_notifications",
                0
            )
            Log.i("[Notifications Manager] Are notifications allowed on lock screen? ${showLockScreenNotification != 0} ($showLockScreenNotification)")
        } catch (e: Exception) {
            Log.e("[Notifications Manager] Failed to get android.provider.Settings.Secure.getInt(lock_screen_show_notifications): $e")
        }

        val notifiable = getNotifiableForCall(call)
        if (notifiable.notificationId == currentForegroundServiceNotificationId) {
            Log.e("[Notifications Manager] There is already a Service foreground notification for an incoming call, cancelling it")
            cancel(notifiable.notificationId)
            currentForegroundServiceNotificationId = 0
        }

        val incomingCallNotificationIntent = Intent(context, Module_Phone_IncomingCallActivity::class.java)
        incomingCallNotificationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_NO_USER_ACTION)
        val pendingIntent = PendingIntent.getActivity(
            context,
            0,
            incomingCallNotificationIntent,
            PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE
        )

        val notification = Module_Phone_Compatibility.createIncomingCallNotification(
            context,
            call,
            notifiable,
            pendingIntent,
            this
        )
        Log.i("[Notifications Manager] Notifying incoming call notification [${notifiable.notificationId}]")
        val manufacturer = Build.MANUFACTURER.lowercase(Locale.getDefault())
        Log.e("[Send Notification] ****** devices is $manufacturer")
//        if (Version.sdkAboveOrEqual(Version.API31_ANDROID_12) && manufacturer == "xiaomi") {
//            notification.headsUpContentView = getRemoteViews()
//        }
        notify(notifiable.notificationId, notification)

        if (useAsForeground) {
            Log.i("[Notifications Manager] Notifying incoming call notification for foreground service [${notifiable.notificationId}]")
            startForeground(notifiable.notificationId, notification)
        }
    }

//    private fun getRemoteViews(): RemoteViews {
//        val views = RemoteViews(context.packageName,R.layout.incoming_heads_up_view)
//
//        return views
//    }

    private fun displayMissedCallNotification(remoteAddress: Address) {
        val missedCallCount: Int = coreContext.core.missedCallsCount
        val body: String
        if (missedCallCount > 1) {
            body = context.getString(R.string.missed_calls_notification_body)
                .format(missedCallCount)
            Log.i("[Notifications Manager] Updating missed calls notification count to $missedCallCount")
        } else {
            val friend: Friend? = coreContext.contactsManager.findContactByAddress(remoteAddress)
            body = context.getString(R.string.missed_call_notification_body)
                .format(friend?.name ?: Module_Phone_LinphoneUtils.getDisplayName(remoteAddress))
            Log.i("[Notifications Manager] Creating missed call notification")
        }

//        val pendingIntent = NavDeepLinkBuilder(context)
//            .setComponentName(MainActivity::class.java)
//            .setGraph(R.navigation.main_nav_graph)
//            .setDestination(R.id.masterCallLogsFragment)
//            .createPendingIntent()
//        val pendingIntent = NavDeepLinkBuilder(context)
//            .setComponentName(MainActivity::class.java)
//            .setGraph(org.linphone.core.R.navigation.main_nav_graph)
//            .setDestination(org.linphone.core.R.id.dialerFragment)
//            .createPendingIntent()
        val postcard = ARouter.getInstance().build(Base_RouterPath.PATH_NOTIFY)
        LogisticsCenter.completion(postcard)
        //跳转到 记录->呼叫记录
        val intent = Intent(context, postcard.destination).apply {
            putExtra(NL_Key.TAB_INDEX, 3)
            putExtra(NL_Key.SUB_TAB_INDEX, 1)
        }
        var pendingIntent: PendingIntent
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
            pendingIntent = PendingIntent.getActivity(
                context,
                100,
                intent,
                PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_ONE_SHOT
            );
        } else {
            pendingIntent =
                PendingIntent.getActivity(context, 101, intent, PendingIntent.FLAG_ONE_SHOT);
        }
        val builder = NotificationCompat.Builder(
            context, context.getString(R.string.notification_channel_missed_call_id)
        )
            .setContentTitle(context.getString(R.string.missed_call_notification_title))
            .setContentText(body)
            .setSmallIcon(R.mipmap.topbar_missed_call_notification)
            .setAutoCancel(true)
            // .setCategory(NotificationCompat.CATEGORY_EVENT) No one really matches "missed call"
            .setVisibility(NotificationCompat.VISIBILITY_PRIVATE)
            .setWhen(System.currentTimeMillis())
            .setShowWhen(true)
            .setNumber(missedCallCount)
            .setColor(ContextCompat.getColor(context, R.color.colorPrimary))

        if (!corePreferences.preventInterfaceFromShowingUp) {
            builder.setContentIntent(pendingIntent)
        }

        val notification = builder.build()
        notify(MISSED_CALLS_NOTIF_ID, notification, MISSED_CALL_TAG)
    }

    fun dismissMissedCallNotification() {
        cancel(MISSED_CALLS_NOTIF_ID, MISSED_CALL_TAG)
    }

    //当前通话是否在监控中
    fun isCurrentCallInMonitoring(call: Call): Boolean {
        return call.params.getCustomHeader(NL_Key.CALL_INFO) == NL_Key.EXTRA_MONITOR
    }

    fun displayCallNotification(call: Call, useAsForeground: Boolean = false) {
        if (isCurrentCallInMonitoring(call)) return
        val notifiable = getNotifiableForCall(call)

        val callActivity: Class<*> = when (call.state) {
            Call.State.OutgoingRinging, Call.State.OutgoingProgress, Call.State.OutgoingInit, Call.State.OutgoingEarlyMedia -> {
                Module_Phone_OutgoingCallActivity::class.java
            }

            else -> {
                if (call.params.getCustomHeader(NL_Key.CALL_INFO) == NL_Key.EXTRA_MONITOR) {
                    Module_Phone_MonitorActivityNL::class.java
                } else {
                    Module_Phone_CallActivity::class.java
                }
            }
        }

        val serviceChannel = context.getString(R.string.notification_channel_service_id)
        val channelToUse = when (val serviceChannelImportance =
            Module_Phone_Compatibility.getChannelImportance(notificationManager, serviceChannel)) {
            NotificationManagerCompat.IMPORTANCE_NONE -> {
                Log.w("[Notifications Manager] Service channel is disabled, using incoming call channel instead!")
                context.getString(R.string.notification_channel_incoming_call_id)
            }

            NotificationManagerCompat.IMPORTANCE_LOW -> {
                // Expected, nothing to do
                serviceChannel
            }

            else -> {
                // If user disables & enabled back service notifications channel, importance won't be low anymore but default!
                Log.w("[Notifications Manager] Service channel importance is $serviceChannelImportance and not LOW (${NotificationManagerCompat.IMPORTANCE_LOW}) as expected!")
                serviceChannel
            }
        }

        val callNotificationIntent = Intent(context, callActivity)
        callNotificationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
        val pendingIntent = PendingIntent.getActivity(
            context,
            0,
            callNotificationIntent,
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
        )

        val notification = Module_Phone_Compatibility.createCallNotification(
            context,
            call,
            notifiable,
            pendingIntent,
            channelToUse,
            this
        )
        Log.i("[Notifications Manager] Notifying call notification [${notifiable.notificationId}]")

        notify(notifiable.notificationId, notification)

        if (useAsForeground || (service != null && currentForegroundServiceNotificationId == 0)) {
            Log.i("[Notifications Manager] Notifying call notification for foreground service [${notifiable.notificationId}]")
            startForeground(notifiable.notificationId, notification)
        }
    }

    private fun dismissCallNotification(call: Call) {
        val address = call.remoteAddress.asStringUriOnly().split("?")[0]
        val notifiable: Notifiable? = callNotificationsMap[address]
        if (notifiable != null) {
            cancel(notifiable.notificationId)
            callNotificationsMap.remove(address)
        } else {
            Log.w("[Notifications Manager] No notification found for call ${call.callLog.callId}")
        }
    }

    private fun cancelMissCallNotification() {

    }

    /* Chat related */

    private fun getNotificationIdForChat(chatRoom: ChatRoom): Int {
        return chatRoom.creationTime.toInt()
    }

    private fun displayChatNotifiable(room: ChatRoom, notifiable: Notifiable) {
//        val localAddress = room.localAddress.asStringUriOnly()
//        val peerAddress = room.peerAddress.asStringUriOnly()
//        val args = Bundle()
//        args.putString("RemoteSipUri", peerAddress)
//        args.putString("LocalSipUri", localAddress)
//
////        val pendingIntent = NavDeepLinkBuilder(context)
////            .setComponentName(MainActivity::class.java)
////            .setGraph(R.navigation.main_nav_graph)
////            .setDestination(R.id.masterChatRoomsFragment)
////            .setArguments(args)
////            .createPendingIntent()
//        val pendingIntent = NavDeepLinkBuilder(context)
//            .setComponentName(MainActivity::class.java)
//            .setGraph(org.linphone.core.R.navigation.main_nav_graph)
//            .setDestination(org.linphone.core.R.id.dialerFragment)
//            .createPendingIntent()
//
//        // PendingIntents attached to bubbles must be mutable
//        val target = Intent(context, ChatBubbleActivity::class.java)
//        target.putExtra("RemoteSipUri", peerAddress)
//        target.putExtra("LocalSipUri", localAddress)
//        val bubbleIntent = PendingIntent.getActivity(
//            context,
//            notifiable.notificationId,
//            target,
//            Compatibility.getUpdateCurrentPendingIntentFlag()
//        )
//
//        val id = LinphoneUtils.getChatRoomId(room.localAddress, room.peerAddress)
//        val notification = createMessageNotification(notifiable, pendingIntent, bubbleIntent, id)
//        notify(notifiable.notificationId, notification, CHAT_TAG)
    }

    private fun displayIncomingChatNotification(room: ChatRoom, message: ChatMessage) {
        val notifiable = getNotifiableForRoom(room)
        if (notifiable.messages.isNotEmpty() || room.unreadMessagesCount == 1) {
            val friend = coreContext.contactsManager.findContactByAddress(message.fromAddress)
            val notifiableMessage = getNotifiableMessage(message, friend)
            notifiable.messages.add(notifiableMessage)
        } else {
            for (chatMessage in room.unreadHistory) {
                val friend =
                    coreContext.contactsManager.findContactByAddress(chatMessage.fromAddress)
                val notifiableMessage = getNotifiableMessage(chatMessage, friend)
                notifiable.messages.add(notifiableMessage)
            }
        }

        if (room.hasCapability(ChatRoomCapabilities.OneToOne.toInt())) {
            notifiable.isGroup = false
        } else {
            notifiable.isGroup = true
            notifiable.groupTitle = room.subject
        }

        displayChatNotifiable(room, notifiable)
    }

    private fun getNotifiableForRoom(room: ChatRoom): Notifiable {
        val address = room.peerAddress.asStringUriOnly()
        var notifiable: Notifiable? = chatNotificationsMap[address]
        if (notifiable == null) {
            notifiable = Notifiable(getNotificationIdForChat(room))
            notifiable.myself = Module_Phone_LinphoneUtils.getDisplayName(room.localAddress)
            notifiable.localIdentity = room.localAddress.asStringUriOnly()
            notifiable.remoteAddress = room.peerAddress.asStringUriOnly()

            chatNotificationsMap[address] = notifiable
        }
        return notifiable
    }

    private fun getNotifiableMessage(message: ChatMessage, friend: Friend?): NotifiableMessage {
        val roundPicture = Module_Phone_ImageUtils.getRoundBitmapFromUri(context, friend?.getThumbnailUri())
        val displayName = friend?.name ?: Module_Phone_LinphoneUtils.getDisplayName(message.fromAddress)

        var text: String = message.contents.find { content -> content.isText }?.utf8Text ?: ""
        if (text.isEmpty()) {
            for (content in message.contents) {
                text += content.name
            }
        }

        val notifiableMessage = NotifiableMessage(
            text,
            friend,
            displayName,
            message.time,
            senderAvatar = roundPicture,
            isOutgoing = message.isOutgoing
        )

        for (content in message.contents) {
            if (content.isFile) {
                val path = content.filePath
                if (path != null) {
                    val contentUri: Uri = Module_Phone_FileUtils.getPublicFilePath(context, path)
                    val filePath: String = contentUri.toString()
                    val extension = Module_Phone_FileUtils.getExtensionFromFileName(filePath)
                    if (extension.isNotEmpty()) {
                        val mime =
                            MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)
                        notifiableMessage.filePath = contentUri
                        notifiableMessage.fileMime = mime
                        Log.i("[Notifications Manager] Added file $contentUri with MIME $mime to notification")
                    } else {
                        Log.e("[Notifications Manager] Couldn't find extension for incoming message with file $path")
                    }
                }
            }
        }

        return notifiableMessage
    }

    private fun displayReplyMessageNotification(message: ChatMessage, notifiable: Notifiable) {
        Log.i("[Notifications Manager] Updating message notification with reply for notification ${notifiable.notificationId}")

        val text = message.contents.find { content -> content.isText }?.utf8Text ?: ""
        val reply = NotifiableMessage(
            text,
            null,
            notifiable.myself ?: Module_Phone_LinphoneUtils.getDisplayName(message.fromAddress),
            System.currentTimeMillis(),
            isOutgoing = true
        )
        notifiable.messages.add(reply)

        displayChatNotifiable(message.chatRoom, notifiable)
    }

    fun dismissChatNotification(room: ChatRoom): Boolean {
        val address = room.peerAddress.asStringUriOnly()
        val notifiable: Notifiable? = chatNotificationsMap[address]
        if (notifiable != null) {
            Log.i("[Notifications Manager] Dismissing notification for chat room $room with id ${notifiable.notificationId}")
            notifiable.messages.clear()
            cancel(notifiable.notificationId, CHAT_TAG)
            return true
        } else {
            val previousNotificationId =
                previousChatNotifications.find { id -> id == room.creationTime.toInt() }
            if (previousNotificationId != null) {
                if (chatBubbleNotifications.contains(previousNotificationId)) {
                    Log.i("[Notifications Manager] Found previous notification with same ID [$previousNotificationId] but not cancelling it as it's ID is in chat bubbles list")
                } else {
                    Log.i("[Notifications Manager] Found previous notification with same ID [$previousNotificationId], canceling it")
                    cancel(previousNotificationId, CHAT_TAG)
                }
                return true
            }
        }
        return false
    }

    fun changeDismissNotificationUponReadForChatRoom(chatRoom: ChatRoom, dismiss: Boolean) {
        val notificationId = chatRoom.creationTime.toInt()
        if (dismiss) {
            Log.i("[Notifications Manager] Allow notification with id [$notificationId] to be dismissed when chat room will be marked as read, used for chat bubble")
            chatBubbleNotifications.add(notificationId)
        } else {
            Log.i("[Notifications Manager] Prevent notification with id [$notificationId] from being dismissed when chat room will be marked as read, used for chat bubble")
            chatBubbleNotifications.remove(notificationId)
        }
    }

    /* Notifications */

    private fun createMessageNotification(
        notifiable: Notifiable,
        pendingIntent: PendingIntent,
        bubbleIntent: PendingIntent,
        id: String
    ): Notification {
        val me = Person.Builder().setName(notifiable.myself).build()
        val style = NotificationCompat.MessagingStyle(me)
        val largeIcon: Bitmap? = notifiable.messages.lastOrNull()?.senderAvatar

        var lastPerson: Person? = null
        for (message in notifiable.messages) {
            val friend = message.friend
            val person = getPerson(friend, message.sender, message.senderAvatar)

            // We don't want to see our own avatar
            if (!message.isOutgoing) {
                lastPerson = person
            }

            val msg = if (!corePreferences.hideChatMessageContentInNotification) {
                NotificationCompat.MessagingStyle.Message(message.message, message.time, person)
            } else {
                NotificationCompat.MessagingStyle.Message(
                    Module_Phone_AppUtils.getString(R.string.chat_message_notification_hidden_content),
                    message.time,
                    person
                )
            }

            if (message.filePath != null && !corePreferences.hideChatMessageContentInNotification) {
                msg.setData(message.fileMime, message.filePath)
            }

            style.addMessage(msg)
        }

        if (notifiable.isGroup) {
            style.conversationTitle = notifiable.groupTitle
        }
        style.isGroupConversation = notifiable.isGroup

        val icon =
            lastPerson?.icon ?: IconCompat.createWithResource(context, R.drawable.head_in_call)
        val bubble = NotificationCompat.BubbleMetadata.Builder(bubbleIntent, icon)
            .setDesiredHeightResId(R.dimen.chat_message_bubble_desired_height)
            .build()

        val notificationBuilder = NotificationCompat.Builder(
            context,
            context.getString(R.string.notification_channel_chat_id)
        )
            .setSmallIcon(R.mipmap.topbar_chat_notification)
            .setAutoCancel(true)
            .setLargeIcon(largeIcon)
            .setCategory(NotificationCompat.CATEGORY_MESSAGE)
            .setGroup(CHAT_NOTIFICATIONS_GROUP)
            .setVisibility(NotificationCompat.VISIBILITY_PRIVATE)
            .setNumber(notifiable.messages.size)
            .setWhen(System.currentTimeMillis())
            .setShowWhen(true)
            .setStyle(style)
            .setColor(ContextCompat.getColor(context, R.color.colorPrimary))
            .addAction(getReplyMessageAction(notifiable))
            .addAction(getMarkMessageAsReadAction(notifiable))
            .setShortcutId(id)
            .setLocusId(LocusIdCompat(id))

        if (!corePreferences.preventInterfaceFromShowingUp) {
            notificationBuilder.setContentIntent(pendingIntent)
        }

        if (corePreferences.markAsReadUponChatMessageNotificationDismissal) {
            Log.i("[Notifications Manager] Chat room will be marked as read when notification will be dismissed")
            notificationBuilder
                .setDeleteIntent(getMarkMessageAsReadPendingIntent(notifiable))
        }

        if (!Module_Phone_Compatibility.canChatMessageChannelBubble(context)) {
            Log.w("[Notifications Manager] This conversation wasn't granted bubble permission yet")
        }
        // We still need to set the bubbleMetadata, otherwise user won't ever be able to enable bubbles!
        notificationBuilder.bubbleMetadata = bubble
        return notificationBuilder.build()
    }

    /* Notifications actions */
    fun getCallAnswerPendingIntentBackUp(notifiable: Notifiable): PendingIntent {
        Log.e("**************getCallAnswerPendingIntent")
        val answerIntent = Intent(context, Module_Phone_NotificationBroadcastReceiver::class.java)
        answerIntent.action = INTENT_ANSWER_CALL_NOTIF_ACTION
        answerIntent.putExtra(INTENT_NOTIF_ID, notifiable.notificationId)
        answerIntent.putExtra(INTENT_REMOTE_ADDRESS, notifiable.remoteAddress)

        return PendingIntent.getBroadcast(
            context,
            notifiable.notificationId,
            answerIntent,
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
        )
    }

    fun getCallAnswerPendingIntent(notifiable: Notifiable): PendingIntent {
//        Log.e("**************getCallAnswerPendingIntent")
//
//        val intent = Intent(context, CallActivity::class.java)
//        intent.putExtra("REMOTE_ADDRESS", notifiable.remoteAddress)
//        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
//
//        val resultPendingIntent: PendingIntent? = TaskStackBuilder.create(context).run {
//            addNextIntentWithParentStack(intent)
//            getPendingIntent(
//                0,
//                PendingIntent.FLAG_UPDATE_CURRENT or
//                        PendingIntent.FLAG_IMMUTABLE
//            )
//        }
//        return resultPendingIntent!!
        val answerIntent = Intent(context, Module_Phone_NotificationBroadcastReceiver::class.java)
        answerIntent.action = INTENT_ANSWER_CALL_NOTIF_ACTION
        answerIntent.putExtra(INTENT_NOTIF_ID, notifiable.notificationId)
        answerIntent.putExtra(INTENT_REMOTE_ADDRESS, notifiable.remoteAddress)

        return PendingIntent.getBroadcast(
            context,
            notifiable.notificationId,
            answerIntent,
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
        )
    }

    fun getCallAnswerAction(notifiable: Notifiable): NotificationCompat.Action {
        return NotificationCompat.Action.Builder(
            R.drawable.call_audio_start,
            context.getString(R.string.incoming_call_notification_answer_action_label),
            getCallAnswerPendingIntent(notifiable)
        ).build()
    }

    fun getCallDeclinePendingIntent(notifiable: Notifiable): PendingIntent {
        val hangupIntent = Intent(context, Module_Phone_NotificationBroadcastReceiver::class.java)
        hangupIntent.action = INTENT_HANGUP_CALL_NOTIF_ACTION
        hangupIntent.putExtra(INTENT_NOTIF_ID, notifiable.notificationId)
        hangupIntent.putExtra(INTENT_REMOTE_ADDRESS, notifiable.remoteAddress)

        return PendingIntent.getBroadcast(
            context,
            notifiable.notificationId,
            hangupIntent,
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
        )
    }

    fun getCallDeclineAction(notifiable: Notifiable): NotificationCompat.Action {
        return NotificationCompat.Action.Builder(
            R.drawable.call_hangup,
            context.getString(R.string.incoming_call_notification_hangup_action_label),
            getCallDeclinePendingIntent(notifiable)
        ).build()
    }

    private fun getReplyMessageAction(notifiable: Notifiable): NotificationCompat.Action {
        val replyLabel =
            context.resources.getString(R.string.received_chat_notification_reply_label)
        val remoteInput =
            RemoteInput.Builder(KEY_TEXT_REPLY).setLabel(replyLabel).build()

        val replyIntent = Intent(context, Module_Phone_NotificationBroadcastReceiver::class.java)
        replyIntent.action = INTENT_REPLY_NOTIF_ACTION
        replyIntent.putExtra(INTENT_NOTIF_ID, notifiable.notificationId)
        replyIntent.putExtra(INTENT_LOCAL_IDENTITY, notifiable.localIdentity)
        replyIntent.putExtra(INTENT_REMOTE_ADDRESS, notifiable.remoteAddress)

        // PendingIntents attached to actions with remote inputs must be mutable
        val replyPendingIntent = PendingIntent.getBroadcast(
            context,
            notifiable.notificationId,
            replyIntent,
            Module_Phone_Compatibility.getUpdateCurrentPendingIntentFlag()
        )
        return NotificationCompat.Action.Builder(
            R.drawable.chat_send_over,
            context.getString(R.string.received_chat_notification_reply_label),
            replyPendingIntent
        )
            .addRemoteInput(remoteInput)
            .setAllowGeneratedReplies(true)
            .setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_REPLY)
            .build()
    }

    private fun getMarkMessageAsReadPendingIntent(notifiable: Notifiable): PendingIntent {
        val markAsReadIntent = Intent(context, Module_Phone_NotificationBroadcastReceiver::class.java)
        markAsReadIntent.action = INTENT_MARK_AS_READ_ACTION
        markAsReadIntent.putExtra(INTENT_NOTIF_ID, notifiable.notificationId)
        markAsReadIntent.putExtra(INTENT_LOCAL_IDENTITY, notifiable.localIdentity)
        markAsReadIntent.putExtra(INTENT_REMOTE_ADDRESS, notifiable.remoteAddress)

        return PendingIntent.getBroadcast(
            context,
            notifiable.notificationId,
            markAsReadIntent,
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
        )
    }

    private fun getMarkMessageAsReadAction(notifiable: Notifiable): NotificationCompat.Action {
        val markAsReadPendingIntent = getMarkMessageAsReadPendingIntent(notifiable)
        return NotificationCompat.Action.Builder(
            R.drawable.chat_send_over,
            context.getString(R.string.received_chat_notification_mark_as_read_label),
            markAsReadPendingIntent
        )
            .setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_MARK_AS_READ)
            .build()
    }
}